{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "39257551-4470-46e0-bb17-b48459e7c742",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "assert(torch.cuda.is_available())"
   ]
  },
  {
   "attachments": {
    "3478af58-3d72-4ed7-98d9-90bfa5adc494.png": {
     "image/png": "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"
    }
   },
   "cell_type": "markdown",
   "id": "98b1e830-7580-4a9c-b7d5-e931c5f4778f",
   "metadata": {},
   "source": [
    "![image.png](attachment:3478af58-3d72-4ed7-98d9-90bfa5adc494.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0ba6ab91-38f6-43e5-85ce-35b137a700a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Focus(nn.Module):\n",
    "    # Focus wh information into c-space\n",
    "    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups\n",
    "        super(Focus, self).__init__()\n",
    "        self.conv = nn.Conv(c1 * 4, c2, k, s, p, g, act)      # 这里输入通道变成了4倍\n",
    "\n",
    "    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)\n",
    "        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3b566ca4-e12c-45e7-9865-78d8b27e022f",
   "metadata": {},
   "outputs": [],
   "source": [
    "class FocusWithoutConv(nn.Module):\n",
    "    # Focus wh information into c-space\n",
    "    def __init__(self):  # ch_in, ch_out, kernel, stride, padding, groups\n",
    "        super(FocusWithoutConv, self).__init__()\n",
    "\n",
    "    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)\n",
    "        return torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d635be37-e2cf-4cd1-969c-a651b2bafad8",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Defocus(nn.Module):\n",
    "    \"\"\"\n",
    "    Defocus a `focus`ed tensor. \n",
    "    或许叫ReverseFocus更好听一点但是名字太长了……\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        super(Defocus, self).__init__()\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \" 反解focus \"\n",
    "        blank = torch.zeros(x.shape[0], x.shape[1]//4, x.shape[2]*2, x.shape[3]*2).to(x.device)\n",
    "\n",
    "        blank[..., ::2, ::2] = x[:, :3, :, :] # 这里的3应该是channel_in，为了简单先写死了\n",
    "        blank[..., 1::2, ::2] = x[:, 3:6, :, :]\n",
    "        blank[..., ::2, 1::2] = x[:, 6:9, :, :]\n",
    "        blank[..., 1::2, 1::2] = x[:, 9:12, :, :]\n",
    "\n",
    "        return blank\n",
    "        \n",
    "class Model(nn.Module):\n",
    "    \" Glue them together. \"\n",
    "    def __init__(self, focus, defocus):\n",
    "        super(Model, self).__init__()\n",
    "        self.focus = focus\n",
    "        self.defocus = defocus\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \" Focus一下，再Defocus，把两个向量都返回（证明it works）\"\n",
    "        focused = self.focus(x)\n",
    "        defocused = self.defocus(focused)\n",
    "        return focused, defocused\n",
    "    \n",
    "model = Model(FocusWithoutConv(), Defocus())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e1a2b45d-6eca-4e1f-bf73-672d0ddccd70",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.arange(1*3*4*4).reshape(1, 3, 4, 4).float()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e67d16d3-727d-4ba3-af9e-df156e37a02f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[ 0.,  1.,  2.,  3.],\n",
       "          [ 4.,  5.,  6.,  7.],\n",
       "          [ 8.,  9., 10., 11.],\n",
       "          [12., 13., 14., 15.]],\n",
       "\n",
       "         [[16., 17., 18., 19.],\n",
       "          [20., 21., 22., 23.],\n",
       "          [24., 25., 26., 27.],\n",
       "          [28., 29., 30., 31.]],\n",
       "\n",
       "         [[32., 33., 34., 35.],\n",
       "          [36., 37., 38., 39.],\n",
       "          [40., 41., 42., 43.],\n",
       "          [44., 45., 46., 47.]]]])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "4e42cfcc-a02d-4fe8-9433-d9b70a9a4888",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([[[[ 0.,  2.],\n",
      "          [ 8., 10.]],\n",
      "\n",
      "         [[16., 18.],\n",
      "          [24., 26.]],\n",
      "\n",
      "         [[32., 34.],\n",
      "          [40., 42.]],\n",
      "\n",
      "         [[ 4.,  6.],\n",
      "          [12., 14.]],\n",
      "\n",
      "         [[20., 22.],\n",
      "          [28., 30.]],\n",
      "\n",
      "         [[36., 38.],\n",
      "          [44., 46.]],\n",
      "\n",
      "         [[ 1.,  3.],\n",
      "          [ 9., 11.]],\n",
      "\n",
      "         [[17., 19.],\n",
      "          [25., 27.]],\n",
      "\n",
      "         [[33., 35.],\n",
      "          [41., 43.]],\n",
      "\n",
      "         [[ 5.,  7.],\n",
      "          [13., 15.]],\n",
      "\n",
      "         [[21., 23.],\n",
      "          [29., 31.]],\n",
      "\n",
      "         [[37., 39.],\n",
      "          [45., 47.]]]]), tensor([[[[ 0.,  1.,  2.,  3.],\n",
      "          [ 4.,  5.,  6.,  7.],\n",
      "          [ 8.,  9., 10., 11.],\n",
      "          [12., 13., 14., 15.]],\n",
      "\n",
      "         [[16., 17., 18., 19.],\n",
      "          [20., 21., 22., 23.],\n",
      "          [24., 25., 26., 27.],\n",
      "          [28., 29., 30., 31.]],\n",
      "\n",
      "         [[32., 33., 34., 35.],\n",
      "          [36., 37., 38., 39.],\n",
      "          [40., 41., 42., 43.],\n",
      "          [44., 45., 46., 47.]]]]))\n"
     ]
    }
   ],
   "source": [
    "print(model(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ccf389b5-936e-46a5-ab54-c7cc681589f9",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[[[  0.,   1.,   2.,   3.],\n",
      "          [ 10.,  11.,  12.,  13.],\n",
      "          [ 20.,  21.,  22.,  23.],\n",
      "          [ 30.,  31.,  32.,  33.]],\n",
      "\n",
      "         [[100., 101., 102., 103.],\n",
      "          [110., 111., 112., 113.],\n",
      "          [120., 121., 122., 123.],\n",
      "          [130., 131., 132., 133.]],\n",
      "\n",
      "         [[200., 201., 202., 203.],\n",
      "          [210., 211., 212., 213.],\n",
      "          [220., 221., 222., 223.],\n",
      "          [230., 231., 232., 233.]]]]) torch.Size([1, 3, 4, 4])\n"
     ]
    }
   ],
   "source": [
    "x2 = torch.arange(4).reshape(1, 1, 1, 4) + torch.arange(4).mul(10).reshape(1, 1, 4, 1) + torch.arange(3).mul(100).reshape(1, 3, 1, 1)\n",
    "x2 = x2.float()\n",
    "print(x2, x2.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "81130a6a-0b05-4162-b7cc-da1af5e2a672",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([[[[  0.,   2.],\n",
       "           [ 20.,  22.]],\n",
       " \n",
       "          [[100., 102.],\n",
       "           [120., 122.]],\n",
       " \n",
       "          [[200., 202.],\n",
       "           [220., 222.]],\n",
       " \n",
       "          [[ 10.,  12.],\n",
       "           [ 30.,  32.]],\n",
       " \n",
       "          [[110., 112.],\n",
       "           [130., 132.]],\n",
       " \n",
       "          [[210., 212.],\n",
       "           [230., 232.]],\n",
       " \n",
       "          [[  1.,   3.],\n",
       "           [ 21.,  23.]],\n",
       " \n",
       "          [[101., 103.],\n",
       "           [121., 123.]],\n",
       " \n",
       "          [[201., 203.],\n",
       "           [221., 223.]],\n",
       " \n",
       "          [[ 11.,  13.],\n",
       "           [ 31.,  33.]],\n",
       " \n",
       "          [[111., 113.],\n",
       "           [131., 133.]],\n",
       " \n",
       "          [[211., 213.],\n",
       "           [231., 233.]]]]),\n",
       " tensor([[[[  0.,   1.,   2.,   3.],\n",
       "           [ 10.,  11.,  12.,  13.],\n",
       "           [ 20.,  21.,  22.,  23.],\n",
       "           [ 30.,  31.,  32.,  33.]],\n",
       " \n",
       "          [[100., 101., 102., 103.],\n",
       "           [110., 111., 112., 113.],\n",
       "           [120., 121., 122., 123.],\n",
       "           [130., 131., 132., 133.]],\n",
       " \n",
       "          [[200., 201., 202., 203.],\n",
       "           [210., 211., 212., 213.],\n",
       "           [220., 221., 222., 223.],\n",
       "           [230., 231., 232., 233.]]]]))"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model(x2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f57fe4a1-22d6-4088-a275-3f19fbde9688",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([[[[ 0.,  1.,  2.,  3.],\n",
       "           [ 4.,  5.,  6.,  7.],\n",
       "           [ 8.,  9., 10., 11.],\n",
       "           [12., 13., 14., 15.]],\n",
       " \n",
       "          [[16., 17., 18., 19.],\n",
       "           [20., 21., 22., 23.],\n",
       "           [24., 25., 26., 27.],\n",
       "           [28., 29., 30., 31.]],\n",
       " \n",
       "          [[32., 33., 34., 35.],\n",
       "           [36., 37., 38., 39.],\n",
       "           [40., 41., 42., 43.],\n",
       "           [44., 45., 46., 47.]]]]),\n",
       " torch.Size([1, 3, 4, 4]))"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x, x.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "694e958a-b96a-4be2-945b-91099b541328",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 0.],\n",
      "        [0., 0.],\n",
      "        [0., 1.],\n",
      "        [0., 0.]])\n"
     ]
    }
   ],
   "source": [
    "leftmat = torch.zeros(x.shape[-2], x.shape[-2]//2).float()\n",
    "leftmat[::2, :] = torch.eye(x.shape[-2]//2)\n",
    "print(leftmat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "91bb51f0-b4b5-45e0-b08c-67960b8452a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 0., 0., 0.],\n",
      "        [0., 0., 1., 0.]])\n"
     ]
    }
   ],
   "source": [
    "if x.shape[-2] == x.shape[-1]:\n",
    "    rightmat = leftmat.T\n",
    "    \n",
    "print(rightmat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "4541314c-7259-4700-b12a-ad3f29268a8a",
   "metadata": {},
   "outputs": [],
   "source": [
    "focus = FocusWithoutConv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e9c70ba8-d546-4cf6-8457-9d8c9918423b",
   "metadata": {},
   "outputs": [],
   "source": [
    "fout = focus(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "398a2030-58db-4550-97bd-df0ee7618515",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([[[[ 0.,  2.],\n",
       "           [ 8., 10.]],\n",
       " \n",
       "          [[16., 18.],\n",
       "           [24., 26.]],\n",
       " \n",
       "          [[32., 34.],\n",
       "           [40., 42.]],\n",
       " \n",
       "          [[ 4.,  6.],\n",
       "           [12., 14.]],\n",
       " \n",
       "          [[20., 22.],\n",
       "           [28., 30.]],\n",
       " \n",
       "          [[36., 38.],\n",
       "           [44., 46.]],\n",
       " \n",
       "          [[ 1.,  3.],\n",
       "           [ 9., 11.]],\n",
       " \n",
       "          [[17., 19.],\n",
       "           [25., 27.]],\n",
       " \n",
       "          [[33., 35.],\n",
       "           [41., 43.]],\n",
       " \n",
       "          [[ 5.,  7.],\n",
       "           [13., 15.]],\n",
       " \n",
       "          [[21., 23.],\n",
       "           [29., 31.]],\n",
       " \n",
       "          [[37., 39.],\n",
       "           [45., 47.]]]]),\n",
       " torch.Size([1, 12, 2, 2]))"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fout, fout.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "936e4085-4cb7-43f6-a1cb-46684cd30d45",
   "metadata": {},
   "outputs": [],
   "source": [
    "frame = fout[0, 0, :, :]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "9565c5af-e2a4-4ac4-8288-0c717489b1c5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2, 2])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "frame.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d12f699f-e87b-47b5-bf0e-1810f70c5695",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[ 0.,  0.,  2.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 8.,  0., 10.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.]],\n",
       "\n",
       "        [[16.,  0., 18.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [24.,  0., 26.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.]],\n",
       "\n",
       "        [[32.,  0., 34.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [40.,  0., 42.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.]]])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leftmat @ fout[0, :3, :, :] @ rightmat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "192214d0-ab07-46d2-b5ac-8a71e081cdb8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0., 1., 0., 0.],\n",
       "        [0., 0., 0., 1.]])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rightmat.roll(1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "3f5dbcef-7e68-4d97-a3e7-9e65544ac165",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[ 0.,  0.,  0.,  0.],\n",
       "         [ 4.,  0.,  6.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [12.,  0., 14.,  0.]],\n",
       "\n",
       "        [[ 0.,  0.,  0.,  0.],\n",
       "         [20.,  0., 22.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [28.,  0., 30.,  0.]],\n",
       "\n",
       "        [[ 0.,  0.,  0.,  0.],\n",
       "         [36.,  0., 38.,  0.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [44.,  0., 46.,  0.]]])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leftmat.roll(1, 0) @ fout[0, 3:6, :, :] @ rightmat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "6d0b5e98-11fb-49d8-87e5-fd683a0616ca",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0., 0.],\n",
       "        [1., 0.],\n",
       "        [0., 0.],\n",
       "        [0., 1.]])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leftmat.roll(1, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "6c6c78b1-f953-4e19-a518-922b69acb1b1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[ 0.,  4.,  0.,  6.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 12.,  0., 14.],\n",
       "         [ 0.,  0.,  0.,  0.]],\n",
       "\n",
       "        [[ 0., 20.,  0., 22.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 28.,  0., 30.],\n",
       "         [ 0.,  0.,  0.,  0.]],\n",
       "\n",
       "        [[ 0., 36.,  0., 38.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 44.,  0., 46.],\n",
       "         [ 0.,  0.,  0.,  0.]]])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leftmat @ fout[0, 3:6, :, :] @ rightmat.roll(1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "6b17a2e5-21c8-4d93-b065-24e09c47577b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[ 0.,  0.,  0.,  0.],\n",
       "         [ 0.,  1.,  0.,  3.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0.,  9.,  0., 11.]],\n",
       "\n",
       "        [[ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 17.,  0., 19.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 25.,  0., 27.]],\n",
       "\n",
       "        [[ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 33.,  0., 35.],\n",
       "         [ 0.,  0.,  0.,  0.],\n",
       "         [ 0., 41.,  0., 43.]]])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leftmat.roll(1, 0) @ fout[0, 6:9, :, :] @ rightmat.roll(1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "70569206-7fc7-4164-9874-f2a6c08b08ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "class DefocusMatrixified(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(DefocusMatrixified, self).__init__()\n",
    "\n",
    "    def forward(self, x):\n",
    "        leftmat = torch.zeros(x.shape[-2]*2, x.shape[-2]).float().to(device=x.device)\n",
    "        leftmat[::2, :] = torch.eye(x.shape[-2])\n",
    "        if x.shape[-2] == x.shape[-1]:\n",
    "            rightmat = leftmat.T\n",
    "        else:\n",
    "            rightmat = torch.zeros(x.shape[-1], x.shape[-1]*2).float().to(device=x.device)\n",
    "            rightmat[:, ::2] = torch.eye(x.shape[-1])\n",
    "        \n",
    "        return leftmat @ x[:, :3, :, :] @ rightmat +\\\n",
    "                leftmat.roll(1, 0) @ x[:, 3:6, :, :] @ rightmat +\\\n",
    "                leftmat @ x[:, 6:9, :, :] @ rightmat.roll(1, 1) +\\\n",
    "                leftmat.roll(1, 0) @ x[:, 9:12, :, :] @ rightmat.roll(1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "34fdcf19-4606-4f78-9611-deb823c85611",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Model(FocusWithoutConv(), Defocus())\n",
    "f, d = model(x)\n",
    "assert torch.all(d == x)\n",
    "\n",
    "model_matrix = Model(FocusWithoutConv(), DefocusMatrixified())\n",
    "f, d = model_matrix(x)\n",
    "assert torch.all(d == x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "8668e8dc-bcb8-4016-9a3f-6e7c004cd1cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cpu\n",
      "0:00:00.002236\n",
      "0:00:00.000922\n"
     ]
    }
   ],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "print(x.device)\n",
    "tic = datetime.now()\n",
    "model_matrix(x)\n",
    "print(datetime.now()-tic)\n",
    "\n",
    "tic = datetime.now()\n",
    "model(x)\n",
    "print(datetime.now()-tic)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "97fa5bdd-c29d-4fe8-b996-f391ce330a64",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_cuda = x.cuda()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "8d0f5193-90b9-4230-9a58-9aaee5895e09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda:0\n",
      "0:00:00.002054\n",
      "0:00:00.000397\n"
     ]
    }
   ],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "print(x_cuda.device)\n",
    "\n",
    "tic = datetime.now()\n",
    "model_matrix(x_cuda)\n",
    "print(datetime.now()-tic)\n",
    "\n",
    "tic = datetime.now()\n",
    "model(x_cuda)\n",
    "print(datetime.now()-tic)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
